<?php
/**
 * 阿里云OSS
 * User: 13651
 * Time: 2025/9/9 17:39
 */

namespace App\Extensions\oss;

use App\Extensions\BaseService;
use App\Traits\HttpHelper;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use OSS\Core\OssException;
use OSS\Core\OssUtil;
use OSS\Tests\OssUtilTest;


class Oss extends BaseService
{
    use HttpHelper;

    /**
     * 上传文件
     * @param string $filename
     * @param string $filePath
     * @param int $timeout
     * @return string
     */
    public function uploadDoc(string $filename, string $filePath,int $timeout = 0)
    {
        $date = Carbon::now()->format('Ymd');
        $ossPath = 'doc/' . $date. '/' . $filename;
        return $this->fileUpload($ossPath, $filePath,$timeout);
    }


    /**
     * 使用 oss 上传文件
     * @param string $ossPath
     * @param string $filePath
     * @param int $timeout
     * @return string
     */
    private function upload(string $ossPath, string $filePath,int $timeout = 0)
    {

        try {
            $AccessKeyId = env('ALIOSS_AccessKeyId');
            $AccessKeySecret = env('ALIOSS_AccessKeySecret');
            $endpoint = env('ALIOSS_endpoint');
            $bucket = env('ALIOSS_bucket');
            $domain = env('ALIOSS_domain');

            $ossClient = new \OSS\OssClient($AccessKeyId,
                $AccessKeySecret,
                $endpoint,
                false,
                null,
                null);
            $result = $ossClient->uploadFile($bucket, $ossPath, $filePath);
            if ($timeout)
            {
                // 生成
                $signedUrl = $ossClient->signUrl($bucket,$ossPath,$timeout);
                $data = parse_url($signedUrl);
                parse_str($data['query'],$params);
                if(!empty($params['OSSAccessKeyId'])) {
                    unset($params['OSSAccessKeyId']);
                }
                $url =  $domain . $data['path'] . "?" . http_build_query($params);
                Log::info("[OSS链接]".$url);
                return $url;
            }
            return empty($result['info']['url']) ? '' : $domain . '/' . $ossPath;
        }catch (\Exception $e){
            Log::info('阿里云OSS上传文件异常'.$e->getMessage());
            return '';
        }
    }

    /**
     * 大文件分片式上传
     */
    public function fileUpload(string $ossPath, string $filePath,int $timeout = 0)
    {
        ini_set('memory_limit', '512M');
        $accessKeyId = env('ALIOSS_AccessKeyId');
        $accessKeySecret = env('ALIOSS_AccessKeySecret');
        $endpoint = env('ALIOSS_endpoint');
        $bucket = env('ALIOSS_bucket');
        $domain = env('ALIOSS_domain');

        $ossClient = new \OSS\OssClient($accessKeyId, $accessKeySecret, $endpoint);
        $object = $ossPath; // 在OSS中的路径和文件名
        // 初始化分片上传
        $uploadId = $ossClient->initiateMultipartUpload($bucket, $object);
        Log::info('OSS大文件上传开始');
        /*
         * 步骤2：上传分片。
         */
        // 分片最小值为100 KB，最大值为5 GB。最后一个分片的大小允许小于100 KB。
        $partSize = 2 * 1024 * 1024;
        $uploadFileSize = sprintf('%u',filesize($filePath));
        $pieces = $ossClient->generateMultiuploadParts($uploadFileSize, $partSize);
        $responseUploadPart = array();
        $uploadPosition = 0;
        $isCheckMd5 = true;
        foreach ($pieces as $i => $piece) {
            $fromPos = $uploadPosition + (integer)$piece[$ossClient::OSS_SEEK_TO];
            $toPos = (integer)$piece[$ossClient::OSS_LENGTH] + $fromPos - 1;
            $upOptions = array(
                // 上传文件。
                $ossClient::OSS_FILE_UPLOAD => $filePath,
                // 设置分片号。
                $ossClient::OSS_PART_NUM => ($i + 1),
                // 指定分片上传起始位置。
                $ossClient::OSS_SEEK_TO => $fromPos,
                // 指定文件长度。
                $ossClient::OSS_LENGTH => $toPos - $fromPos + 1,
                // 是否开启MD5校验，true为开启。
                $ossClient::OSS_CHECK_MD5 => $isCheckMd5,
            );
            // 开启MD5校验。
            if ($isCheckMd5) {
                $contentMd5 = OssUtil::getMd5SumForFile($filePath, $fromPos, $toPos);
                $upOptions[$ossClient::OSS_CONTENT_MD5] = $contentMd5;
            }
            try {
                // 上传分片。
                $responseUploadPart[] = $ossClient->uploadPart($bucket, $object, $uploadId, $upOptions);
                Log::info("上传分片part#{$i} OK");
            } catch(OssException $e) {
                Log::info('分片上错错误'.$e->getMessage());
                return '';
            }

        }
        // $uploadParts是由每个分片的ETag和分片号（PartNumber）组成的数组。
        $uploadParts = array();
        foreach ($responseUploadPart as $i => $eTag) {
            $uploadParts[] = array(
                'PartNumber' => ($i + 1),
                'ETag' => $eTag,
            );
        }

        /**
         * 步骤3：完成上传。
         */
        $comOptions['headers'] = array(
            // 指定完成分片上传时是否覆盖同名Object。此处设置为true，表示禁止覆盖同名Object。
            // 'x-oss-forbid-overwrite' => 'true',
            // 如果指定了x-oss-complete-all:yes，则OSS会列举当前uploadId已上传的所有Part，然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。
            // 'x-oss-complete-all'=> 'yes'
        );
        try {
            // 执行completeMultipartUpload操作时，需要提供所有有效的$uploadParts。OSS收到提交的$uploadParts后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
            $result = $ossClient->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts);
            return $result;
        }  catch(OssException $e) {
            Log::info('OSS报错'.$e->getMessage());
            return '';
        }

    }
}